
package ru.kpm.rectpack.represent;

import com.sun.j3d.utils.behaviors.mouse.MouseRotate;
import com.sun.j3d.utils.behaviors.mouse.MouseTranslate;
import com.sun.j3d.utils.behaviors.mouse.MouseWheelZoom;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Text2D;
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.universe.SimpleUniverse;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Group;
import javax.media.j3d.Material;
import javax.media.j3d.Texture;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3f;

/**
 *
 * @author Podorov Alexander
 */

public abstract class Model3D extends Canvas3D
{
    protected static final float SCALE = 0.001f;
    protected static final float THICKNESS = 0.01f;
    protected static final float CYLINDER_DIAMETER = 0.01f;
    protected static final float CONE_RADIUS = 0.02f;
    protected static final float CYLINDER_ADDITION = 0.2f;    

    protected static final int FONT_SIZE = 20;
    protected static final int X_AXIS = 1;
    protected static final int Y_AXIS = 2;
    protected static final int Z_AXIS = 3;

    protected SimpleUniverse universe;
    protected BranchGroup main;

    protected TextureLoader metal;
    protected TextureLoader pack;
    protected TextureLoader wood;

    protected InputData inputData;
    protected OutputData outputData;

    public Model3D(InputData inputData, OutputData outputData)
    {
        super(SimpleUniverse.getPreferredConfiguration());
        universe = new SimpleUniverse(this);
        universe.getViewingPlatform().setNominalViewingTransform();

        metal = new TextureLoader(getClass().getResource("/metal.png"), "RGB", this);
        pack = new TextureLoader(getClass().getResource("/pack.png"), "RGB", this);
        wood = new TextureLoader(getClass().getResource("/wood.png"), "RGB", this);

        this.inputData = inputData;
        this.outputData = outputData;
    }

    protected Group createBox(float length, float width, float height, float x, float y, float z, Texture texture)
    {
        Appearance appearance = new Appearance();        
        appearance.setTexture(texture);        
        Box box = new Box(length * 0.5f, width * 0.5f, height * 0.5f, Box.GENERATE_NORMALS | Box.GENERATE_TEXTURE_COORDS, appearance);
        Transform3D translate = new Transform3D();
        translate.setTranslation(new Vector3f(length * 0.5f + x, width * 0.5f + y, height * 0.5f + z));
        TransformGroup transformGroup = new TransformGroup(translate);
        transformGroup.addChild(box);        
        return transformGroup;
    }

    protected Group createPallet(float length, float width, float height, float x, float y, float z, Texture texture)
    {
        Transform3D translate = new Transform3D();
        translate.setTranslation(new Vector3f(x, y, z));
        TransformGroup root = new TransformGroup(translate);
        
        float deskHeight = height * 0.2f;
        float barHeight = height * 0.4f;
        float deskCountByLength = 5;
        float deskCountByWidth = 3;

        if (length >= width)
        {
            float deskWidth = width / (2 * deskCountByLength - 1);
            float deskStep = (length - deskCountByWidth * deskWidth) / (deskCountByWidth - 1);

            for (int i = 0; i < deskCountByWidth; i++)
            {
                root.addChild(createBox(length, deskWidth, deskHeight, 0.0f, i * 4.0f * deskWidth, 0.0f, texture));
            }

            for (int i = 0; i < deskCountByWidth; i++)
            {
                for (int j = 0; j < deskCountByWidth; j++)
                {
                    root.addChild(createBox(deskWidth, deskWidth, barHeight, i * (deskWidth + deskStep), j * (deskCountByLength - 1) * deskWidth, deskHeight, texture));
                }
            }
            
            for (int i = 0; i < deskCountByWidth; i++)
            {
                root.addChild(createBox(deskWidth, width, deskHeight, i * (deskWidth + deskStep), 0.0f, deskHeight + barHeight, texture));
            }            

            for (int i = 0; i < deskCountByLength; i++)
            {
                root.addChild(createBox(length, deskWidth, deskHeight, 0.0f, i * 2.0f * deskWidth, 2.0f * deskHeight + barHeight, texture));
            }            
        }
        else
        {
            float deskLength = length / (2 * deskCountByLength - 1);
            float deskStep = (width - deskCountByWidth * deskLength) / (deskCountByWidth - 1);

            for (int i = 0; i < deskCountByWidth; i++)
            {
                root.addChild(createBox(deskLength, width, deskHeight, i * 4.0f * deskLength, 0.0f, 0.0f, texture));
            }            

            for (int i = 0; i < deskCountByWidth; i++)
            {
                for (int j = 0; j < deskCountByWidth; j++)
                {
                    root.addChild(createBox(deskLength, deskLength, barHeight, i * 4.0f * deskLength, j * (deskLength + deskStep), deskHeight, texture));
                }
            }

            for (int i = 0; i < deskCountByWidth; i++)
            {
                root.addChild(createBox(length, deskLength, deskHeight, 0.0f, i *(deskLength + deskStep), deskHeight + barHeight, texture));
            }            

            for (int i = 0; i < deskCountByLength; i++)
            {
                root.addChild(createBox(deskLength, width, deskHeight, i * 2.0f * deskLength, 0.0f, 2.0f * deskHeight + barHeight, texture));
            }                        
        }

        return root;
    }

    protected abstract Group createContent();

    protected abstract float[] getTransformation();    

    protected Group createAxises(int type, float length, float width, float height)
    {
        Appearance appearance = new Appearance();
        Material material = new Material();
        appearance.setMaterial(material);
        Transform3D lineTransform = new Transform3D();
        Transform3D arrowTransform = new Transform3D();
        Transform3D textTransform = new Transform3D();
        Text2D text = null;
        float cylinderLength = 0.0f;        

        switch (type)
        {
            case X_AXIS:
                lineTransform.rotZ(Math.toRadians(90.0));
                material.setAmbientColor(new Color3f(Color.BLUE));
                cylinderLength = length + CYLINDER_ADDITION;
                lineTransform.setTranslation(new Vector3f(cylinderLength * 0.5f, 0.0f, 0.0f));
                arrowTransform.rotZ(Math.toRadians(- 90.0));
                arrowTransform.setTranslation(new Vector3f(cylinderLength, 0.0f, 0.0f));
                text = new Text2D("Длина", new Color3f(Color.BLUE), "Tahoma", FONT_SIZE, Font.BOLD);
                textTransform.rotZ(Math.toRadians(- 180.0));
                textTransform.setTranslation(new Vector3f(cylinderLength + CYLINDER_ADDITION * 1.5f, 0.0f, 0.0f));
                break;
            case Y_AXIS:
                material.setAmbientColor(new Color3f(Color.GREEN));
                cylinderLength = width + CYLINDER_ADDITION;
                lineTransform.setTranslation(new Vector3f(0.0f, cylinderLength * 0.5f, 0.0f));
                arrowTransform.setTranslation(new Vector3f(0.0f, cylinderLength, 0.0f));
                text = new Text2D("Ширина", new Color3f(Color.GREEN), "Tahoma", FONT_SIZE, Font.BOLD);
                textTransform.rotZ(Math.toRadians(90.0));
                textTransform.setTranslation(new Vector3f(0.0f, cylinderLength + CYLINDER_ADDITION * 0.5f, 0.0f));
                break;
            case Z_AXIS:
                lineTransform.rotX(Math.toRadians(90.0));
                material.setAmbientColor(new Color3f(Color.RED));
                cylinderLength = height + CYLINDER_ADDITION;
                lineTransform.setTranslation(new Vector3f(0.0f, 0.0f, cylinderLength * 0.5f));
                arrowTransform.rotX(Math.toRadians(90.0));
                arrowTransform.setTranslation(new Vector3f(0.0f, 0.0f, cylinderLength));
                text = new Text2D("Высота", new Color3f(Color.RED), "Tahoma", FONT_SIZE, Font.BOLD);
                textTransform.rotZ(Math.toRadians(- 180.0));
                textTransform.setTranslation(new Vector3f(0.0f, 0.0f, cylinderLength + CYLINDER_ADDITION * 0.5f));
                break;
        }

        Group root = new Group();
        TransformGroup lineGroup = new TransformGroup(lineTransform);
        lineGroup.addChild(new Cylinder(CYLINDER_DIAMETER, cylinderLength , Primitive.GENERATE_NORMALS, appearance));
        TransformGroup arrowGroup = new TransformGroup(arrowTransform);
        arrowGroup.addChild(new Cone(CONE_RADIUS, CYLINDER_ADDITION, Primitive.GENERATE_NORMALS, appearance));
        root.addChild(lineGroup);
        root.addChild(arrowGroup);
        TransformGroup wordsGroup = new TransformGroup(textTransform);
        wordsGroup.addChild(text);
        root.addChild(wordsGroup);
        return root;
    }

    protected BranchGroup createBranchGroup()
    {
        BranchGroup root = new BranchGroup();
        root.setCapability(BranchGroup.ALLOW_DETACH);

        TransformGroup transformGroup = new TransformGroup();
        transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

        transformGroup.addChild(createContent());
            
        transformGroup.setTransform(new Transform3D(getTransformation()));
        root.addChild(transformGroup);

        BoundingSphere sphereBounds = new BoundingSphere(new Point3d(0.0f, 0.0f, 0.0f), 1000.0);
        BoundingBox boxBounds = new BoundingBox(new Point3d(-10.0f, -10.0f, -10.0f), new Point3d(10.0f, 10.0f, 10.0f));
        AmbientLight ambientLight = new AmbientLight();
        ambientLight.setInfluencingBounds(sphereBounds);
        ambientLight.setColor(new Color3f(1.0f, 1.0f, 1.0f));
        DirectionalLight directionalLight = new DirectionalLight();
        directionalLight.setColor(new Color3f(0.5f, 0.5f, 0.5f));
        directionalLight.setInfluencingBounds(sphereBounds);
        root.addChild(ambientLight);
        root.addChild(directionalLight);

        Background background = new Background(0.3f, 0.3f, 0.3f);
        background.setApplicationBounds(boxBounds);
        root.addChild(background);

        MouseRotate rotateBehavior = new MouseRotate();
        rotateBehavior.setTransformGroup(transformGroup);
        rotateBehavior.setSchedulingBounds(sphereBounds);
        MouseTranslate translateBehavior = new MouseTranslate();
        translateBehavior.setTransformGroup(transformGroup);
        translateBehavior.setSchedulingBounds(boxBounds);
        MouseWheelZoom scaleBehavior = new MouseWheelZoom();
        scaleBehavior.setTransformGroup(transformGroup);
        scaleBehavior.setSchedulingBounds(sphereBounds);
        root.addChild(scaleBehavior);
        root.addChild(rotateBehavior);
        root.addChild(translateBehavior);

        root.compile();
        return root;
    }

    public Dimension getPreferredSize()
    {
        return new Dimension(0, 0);        
    }

    public Dimension getMinimumSize()
    {
        return getPreferredSize();
    }    

    public void rebuild()
    {
        if (main != null)
        {
            universe.getLocale().removeBranchGraph(main);            
        }

        main = createBranchGroup();
        universe.addBranchGraph(main);
    }
}
